/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO

Authors: Nitay Megides
		 Rachel Banino

Project: Exercise 2

Using:	LinkedList.h
		General.h

Description:
TestManager keeps all the tests and runs them when requested
oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/


#define _CRT_SECURE_NO_WARNINGS

#include "TestManager.h"
#include "TextFileReader.h"
#include "StringConversionTools.h"
#include "LinkedList.h"
#include "General.h"


#define MAX_COMMAND_LINE 5*MAX_PATH

static PList testsList;
static int successStatusCode;

// Local functions
// ===============
ETestResult RunTestee(const char* exeFilename, const char* arguments, const int timeoutMs, const int successExitCode)
{
	PROCESS_INFORMATION processInfo;
	STARTUPINFO startupInfo;
	DWORD waitResult = 0;
	DWORD exitCode = -1;
	ETestResult result = TEST_FAILED;
	BOOL processCreated = 0;

    FILE* dreak = NULL;
    
	// Command length = exe + space + arguments
	int commandLength = strlen(exeFilename) + strlen(arguments) + 1;
	// String length = command length + null
    char* command = (char*)malloc((commandLength + 1) * sizeof(char));
	if (!VerifyMemory(command))
	{
		return TEST_FAILED;
	}
    
    strcpy(command, exeFilename);
    strcat(command, " ");
    strcat(command, arguments);


	memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
	memset(&startupInfo, 0, sizeof(STARTUPINFO));
	startupInfo.cb = sizeof(startupInfo);


	// Start by creating the testee process
	processCreated = CreateProcess(
		NULL,
		ConvertCharStringToLPTSTR(command),
		NULL,
		NULL,
		FALSE,
		CREATE_NEW_CONSOLE,
		NULL,
		NULL,
		&startupInfo,
		&processInfo);

	// If all went well, wait until it completes
	if (processCreated)
	{
		waitResult = WaitForSingleObject(processInfo.hProcess, timeoutMs);

		// Now discuss the process result

		switch (waitResult)
		{
		case WAIT_OBJECT_0:
			if (GetExitCodeProcess(processInfo.hProcess, &exitCode))
			{
				if (exitCode == successExitCode)
					result = TEST_RESULT_SUCCESS;
				else
					result = TEST_RESULT_COLLAPSED;
			}
			break;
		case WAIT_TIMEOUT:
			result = TEST_RESULT_TIMEOUT;
			break;

		default:
			// The test failed. TEST_FAILED will be returned
			break;
		}
	}

	CloseHandle(processInfo.hProcess);
    CloseHandle(processInfo.hThread);
	free(command);
	return result;
}

ETestResult RunTest(PTestDetails testDetails)
{
	ETestResult result = TEST_FAILED;

	testDetails->TestStartTime = GetTickCount();
	result = RunTestee(testDetails->ExeFilename, testDetails->Arguments, testDetails->Timeout, successStatusCode);
	testDetails->TestEndTime = GetTickCount();

	if (result == TEST_RESULT_SUCCESS)
	{
		switch (CompareFiles(
			ConvertCharStringToLPTSTR(testDetails->ExeOutputFile),
			ConvertCharStringToLPTSTR(testDetails->ReferenceOutputFile)))
		{
		case FILECOMPARE_IDENTICAL:
			result = TEST_RESULT_SUCCESS;
			break;
		case FILECOMPARE_DIFFERENT:
			result = TEST_RESULT_FAILURE;
			break;
		case FILECOMPARE_ERROR:
			result = TEST_FAILED;
			break;
		}
	}

	testDetails->TestResult = result;
	return result;
}

DWORD WINAPI RunTestThreadFunc(LPVOID lpThreadParameter)
{
	// Run the test
	ETestResult testResult = RunTest((PTestDetails)lpThreadParameter);

	if (testResult == TEST_FAILED)
		return THREAD_TEST_ERROR;
	else
		return THREAD_TEST_COMPLETED_SUCCESSFULLY;
}

BOOL RunTestAsync(PTestDetails testDetails)
{
	HANDLE hThread = INVALID_HANDLE_VALUE;
	
	// Create the thread
	hThread = CreateThread(NULL, 0, RunTestThreadFunc, testDetails, CREATE_SUSPENDED, &testDetails->ThreadID);
	if (hThread == INVALID_HANDLE_VALUE)
	{
		ReportThreadError();
		return FALSE;
	}

    testDetails->ThreadHandle = hThread;

	// Run the test
	if (ResumeThread(hThread) == -1)
	{
		ReportThreadError();
		// Something went wrong. Kill thread
		TerminateThread(hThread, THREAD_ERROR);
		CloseHandle(hThread);
		return FALSE;
	}

	return TRUE;
}

void TestResultToString(char* destString, ETestResult testResult)
{
    switch (testResult)
    {
    case TEST_RESULT_SUCCESS:
        strcpy(destString, "Succeeded");
        break;
    case TEST_RESULT_FAILURE:
        strcpy(destString, "Failed");
        break;
    case TEST_RESULT_TIMEOUT:
        strcpy(destString, "Timed Out");
        break;
    case TEST_RESULT_COLLAPSED:
        strcpy(destString, "Collapsed");
        break;
    case TEST_FAILED :
        strcpy(destString, "TEST FAILED");
    }
}

BOOL TestsResults()
{
    PListItem listItem = testsList->Anchor;
    PTestDetails testDetails = NULL;
    HANDLE threadHandle = INVALID_HANDLE_VALUE;
    BOOL result = TRUE;
    DWORD exitCode = 0;

    // Run on the list and perform tests
    while ((listItem != NULL) && result)
    {
        testDetails = (PTestDetails)listItem->Value;
        threadHandle = testDetails->ThreadHandle;
        result = (threadHandle != NULL);
        if (result)
        {
            result = (threadHandle != INVALID_HANDLE_VALUE);
            if (result)
            {
                result = GetExitCodeThread(threadHandle, &exitCode);
                if (result)
                {
                    result = (exitCode == THREAD_TEST_COMPLETED_SUCCESSFULLY);
                    ReportThreadExitCode(exitCode);
                }
            }
        }
        listItem = GetNext(listItem);
    }

    return result;
}

// Global functions
// ===============
BOOL InitializeTestManager( int a_iSuccessStatusCode )
{
    successStatusCode = a_iSuccessStatusCode;

    testsList = InitializeList();

    return VerifyMemory(testsList);
}

void FreeTestManager()
{
    PListItem listItem = testsList->Anchor;
    PTestDetails testDetails = NULL;

    // Release all the handles
    while (listItem != NULL)
    {
        testDetails = (PTestDetails)listItem->Value;
        if (testDetails->ThreadHandle != INVALID_HANDLE_VALUE)
            CloseHandle(testDetails->ThreadHandle);

        listItem = GetNext(listItem);
    }

    FreeList(testsList);
}

BOOL AddTest(int testNumber, const char* exeFilename, const char* arguments, const int timeoutMs, const int successExitCode, const char* outputFilename, const char* referenceOutputFilename)
{
 	// Create a new test item
 	PTestDetails testDetails = (PTestDetails)malloc(sizeof(TTestDetails));
 	PListItem listItem; 
 	if (!VerifyMemory(testDetails))
 		return FALSE;
 	
 	// Add it to the list
    listItem = AddValue(testsList, testDetails);
 	
    testDetails->TestNumber = testNumber;
	testDetails->ExeFilename = (char *)malloc(strlen(exeFilename) + 1);
    if (!VerifyMemory(testDetails->ExeFilename))
        return FALSE;
	strcpy(testDetails->ExeFilename, exeFilename);
	testDetails->Arguments = (char *)malloc(strlen(arguments) + 1);
    if (!VerifyMemory(testDetails->Arguments))
        return FALSE;
	strcpy(testDetails->Arguments, arguments);
	testDetails->ExeOutputFile = (char *)malloc(strlen(outputFilename) + 1);
    if (!VerifyMemory(testDetails->ExeOutputFile))
        return FALSE;
	strcpy(testDetails->ExeOutputFile, outputFilename);
	testDetails->ReferenceOutputFile = (char *)malloc(strlen(referenceOutputFilename) + 1);
    if (!VerifyMemory(testDetails->ReferenceOutputFile))
        return FALSE;
	strcpy(testDetails->ReferenceOutputFile, referenceOutputFilename);
	testDetails->Timeout = timeoutMs;
    testDetails->ThreadID = 0;
    testDetails->ThreadHandle = INVALID_HANDLE_VALUE;

 	return VerifyMemory(listItem);
}

BOOL LoadTests(TCHAR* filename, TCHAR* aExeFilename)
{
    int i;
    char sLoadFilename[MAX_PATH];
    char sExeFilename[MAX_PATH];

    char* argumentList = NULL;
    char* outputFile = NULL;
    char* expectedOutputFile = NULL;
    int timeout = 0;


    BOOL result;
    TextFileReader reader;
    result = TRUE;

    // Convert TCHAR into char and verify success
    if (wcstombs(sLoadFilename, filename, MAX_PATH) != _tcslen(filename))
        return FALSE;
    if (wcstombs(sExeFilename, aExeFilename, MAX_PATH) != _tcslen(aExeFilename))
        return FALSE;

    // Load the tests file
    reader = CreateTextFileReader(sLoadFilename);

    // Read each test
    for (i = 0; i < reader.NumOfLines; i+=4)
    {
        if (reader.WordsInLine[i] == 0)
        {
            // That's it. We're done (and there's a new line at the end of the file)
            break;
        }

        // Verify we at least 4 lines
        if (i + 3 > reader.NumOfLines - 1)
        {
            result = FALSE;
            break;
        }

        // Get all parameters
        argumentList = GetSentence(reader, i, 0, reader.WordsInLine[i] - 1);

        // Verify words number on each parameter
        if ((reader.WordsInLine[i+1] < 1) || (reader.WordsInLine[i+2] < 1) || (reader.WordsInLine[i+3] < 1))
        {
            result = FALSE;
            break;
        }

        // Get all params
        outputFile = GetSentence(reader, i + 1, 0, reader.WordsInLine[i+1] - 1);
        expectedOutputFile = GetSentence(reader, i + 2, 0, reader.WordsInLine[i+2] - 1);
        // Convert timeout to MS
        timeout = atoi((char*)reader.WordsArr[i+3][0]) * 1000;

        if (!AddTest((i / 4) + 1, sExeFilename, argumentList, timeout, successStatusCode, outputFile, expectedOutputFile))
        {
            result = FALSE;
            break;
        }
    }

    DeleteTextFileReader(reader);

    return result;
}

BOOL RunTests()
{
 	PListItem listItem = testsList->Anchor;
	PTestDetails testDetails = NULL;
 	BOOL result = TRUE;
	int i = 0;
    HANDLE* handles = (HANDLE*)malloc(sizeof(HANDLE) * testsList->Count);
    if (!VerifyMemory(handles))
        return FALSE;
 
 	// Run on the list and perform tests
 	while ((listItem != NULL) && result)
 	{
		testDetails = (PTestDetails)listItem->Value;
 		result = result && (RunTestAsync(testDetails));


        // We just copy the handles here, we don't really copy then (No need to free them afterwards)
 		if (result)
 			handles[i] = testDetails->ThreadHandle;

 		listItem = GetNext(listItem);

		i++;
 	}
 
	if (result)
	{
		result = (WaitForMultipleObjects(testsList->Count, handles, TRUE, INFINITE) == WAIT_OBJECT_0);
	}

	free(handles);

	return result;
}

BOOL ReportResult(TCHAR* correctnessFilename, TCHAR* runtimeFilename)
{
    PListItem listItem = testsList->Anchor;
    PTestDetails  testDetails = NULL;
    FILE* correctnessFile = NULL;
    FILE* runtimeFile = NULL;
    char testResultString[MAX_RESULT_STRING];

    // First verify that all tests were completed successfully
    if (!TestsResults())
    {
        ReportTestErrors();
        return FALSE;
    }

    // Verify files
    correctnessFile = _tfopen(correctnessFilename, TEXT("w"));
    if (!VerifyFileOpen(correctnessFile))
        return FALSE;

    runtimeFile = _tfopen(runtimeFilename, TEXT("w"));
    if (!VerifyFileOpen(runtimeFile))
    {
        fclose(correctnessFile);
        return FALSE;
    }

    // Run on the list and perform tests
    while (listItem != NULL)
    {
        testDetails = (PTestDetails)listItem->Value;

        TestResultToString(testResultString, testDetails->TestResult);

        fprintf(correctnessFile, "%d : %s\n", testDetails->TestNumber, testResultString);

		if (testDetails->TestResult != TEST_RESULT_TIMEOUT)
			fprintf(runtimeFile, "%d : %d\n", testDetails->TestNumber, testDetails->TestEndTime - testDetails->TestStartTime);

        listItem = GetNext(listItem);
    }

    fclose(correctnessFile);
    fclose(runtimeFile);

    return TRUE;
}
